package com.hmall.search.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.api.dto.ItemDTO;
import com.hmall.common.domain.PageDTO;
import com.hmall.search.domain.query.ItemPageQuery;
import com.hmall.search.service.ISearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 搜索服务
 *
 * @author Moozy
 * @since 2025/5/10
 **/
@Slf4j
@Service
public class SearchServiceImpl implements ISearchService {

    // 连接 ElasticSearch
    public final RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
            HttpHost.create("http://192.168.100.128:9200")
    ));

    @Override
    public PageDTO<ItemDTO> search(ItemPageQuery query) throws IOException {
        // 分页查询
        Integer pageNo = query.getPageNo();
        Integer pageSize = query.getPageSize();

        log.debug("查询 query: {}, pageNo={}, pageSize={}", query, pageNo, pageSize);

        SearchRequest request = new SearchRequest("items");
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (StrUtil.isNotBlank(query.getKey())) {
            boolQuery.must(QueryBuilders.matchQuery("name", query.getKey()));
        }
        if (StrUtil.isNotBlank(query.getCategory())) {
            boolQuery.filter(QueryBuilders.termQuery("category", query.getCategory()));
        }
        if (StrUtil.isNotBlank(query.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", query.getBrand()));
        }
        if (query.getMinPrice() != null && query.getMaxPrice() != null) {
            boolQuery.filter(
                    QueryBuilders.rangeQuery("price")
                            .gte(query.getMinPrice())
                            .lte(query.getMaxPrice())
            );
        }
        // 排序
        if (StrUtil.isNotBlank(query.getSortBy())) {
            request.source()
                    .sort(query.getSortBy(), query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
        } else {
            request.source()
                    .sort("_score", query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC)
                    .sort("updateTime", query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
        }

        // 构造最终搜索请求
        request.source()
                // * 考虑广告位
                .query(QueryBuilders.functionScoreQuery(boolQuery,
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        QueryBuilders.termQuery("isAD", true),
                                        ScoreFunctionBuilders.weightFactorFunction(100)
                                )
                        }))
                .from((pageNo - 1) * pageSize)
                .size(pageSize);

        // 发送请求，获取结果
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        SearchHits searchHits = response.getHits();
        // 总条数
        long total = searchHits.getTotalHits().value;
        List<ItemDTO> itemDTOList = Arrays.stream(searchHits.getHits())
                .map(hit -> JSONUtil.toBean(hit.getSourceAsString(), ItemDTO.class))
                .collect(Collectors.toList());

        return new PageDTO<>(total, total / pageSize + 1, itemDTOList);
    }

    @Override
    public Map<String, List<String>> filters(ItemPageQuery query) throws IOException {
        String categoryAggName = "categoryAgg";
        String brandAggName = "brandAgg";

        SearchRequest request = new SearchRequest("items");

        if (StrUtil.isNotBlank(query.getKey())) {
            request.source().query(QueryBuilders.matchQuery("name", query.getKey()));
        }

        request.source()
                .size(0)
                .aggregation(AggregationBuilders.terms(categoryAggName).field("category"))
                .aggregation(AggregationBuilders.terms(brandAggName).field("brand"));

        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        Aggregations aggregations = response.getAggregations();
        Terms categoryAgg = aggregations.get(categoryAggName);
        Terms brandAgg = aggregations.get(brandAggName);

        Map<String, List<String>> res = new HashMap<>();
        res.put("category", new ArrayList<>());
        res.put("brand", new ArrayList<>());

        List<? extends Terms.Bucket> categoryAggBuckets = categoryAgg.getBuckets();
        res.get("category")
                .addAll(categoryAggBuckets.stream()
                        .map(Terms.Bucket::getKeyAsString)
                        .collect(Collectors.toList())
                );
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();
        res.get("brand")
                .addAll(brandAggBuckets.stream()
                        .map(Terms.Bucket::getKeyAsString)
                        .collect(Collectors.toList())
                );

        return res;
    }
}
